using System;
using AGG.Image;
using AGG.UI;
using AGG.Transform;
using AGG.VertexSource;
using AGG.RasterizerScanline;

namespace AGG
{
    public class image_resample_application : MatchParentBoundsWidget
    {
        public static ImageBuffer m_SourceImage = new ImageBuffer();
        ScanlineRasterizer g_rasterizer;
        scanline_unpacked_8 g_scanline;
        double            g_x1 = 0;
        double            g_y1 = 0;
        double            g_x2 = 0;
        double            g_y2 = 0;
        GammaLookUpTable m_gamma_lut;
        UI.PolygonEditWidget m_quad;
        AGG.UI.RadioButtonWidget m_trans_type;
        AGG.UI.SliderWidget m_gamma;
        AGG.UI.SliderWidget m_blur;
        double m_old_gamma;

        GuiHalSurface guiSurface;

        image_resample_application(GuiHalSurface guiSurface)
        {
            this.guiSurface = guiSurface;

            m_gamma_lut = new GammaLookUpTable(2.0);
            m_quad = new AGG.UI.PolygonEditWidget(4, 5.0);
            m_trans_type = new AGG.UI.RadioButtonWidget(400, 5.0, 430+170.0, 100.0);
            m_gamma = new AGG.UI.SliderWidget(5.0, 5.0+15*0, 400-5, 10.0+15*0);
            m_blur = new AGG.UI.SliderWidget(5.0, 5.0+15*1, 400-5, 10.0+15*1);
            m_blur.ValueChanged += new SliderWidget.ValueChangedEventHandler(NeedRedraw);
            m_gamma.ValueChanged += new SliderWidget.ValueChangedEventHandler(NeedRedraw);
            m_old_gamma = 2.0;

            g_rasterizer = new ScanlineRasterizer();
            g_scanline = new scanline_unpacked_8();

            m_trans_type.text_size(7);
            m_trans_type.add_item("Affine No Resample");
            m_trans_type.add_item("Affine Resample");
            m_trans_type.add_item("Perspective No Resample LERP");
            m_trans_type.add_item("Perspective No Resample Exact");
            m_trans_type.add_item("Perspective Resample LERP");
            m_trans_type.add_item("Perspective Resample Exact");
            m_trans_type.cur_item(4);
            AddChild(m_trans_type);

            m_gamma.range(0.5, 3.0);
            m_gamma.value(2.0);
            m_gamma.label("Gamma={0:F3}");
            AddChild(m_gamma);

            m_blur.range(0.5, 5.0);
            m_blur.value(1.0);
            m_blur.label("Blur={0:F3}");
            AddChild(m_blur);
        }

        void NeedRedraw(object sender, EventArgs e)
        {
            Invalidate();
        }

        public void OnInitialize()
        {
            g_x1 = 0.0;
            g_y1 = 0.0;
            g_x2 = m_SourceImage.Width;
            g_y2 = m_SourceImage.Height;

            double x1 = g_x1;// * 100.0;
            double y1 = g_y1;// * 100.0;
            double x2 = g_x2;// * 100.0;
            double y2 = g_y2;// * 100.0;

            double dx = Width  / 2.0 - (x2 - x1) / 2.0;
            double dy = Height / 2.0 - (y2 - y1) / 2.0;
            m_quad.SetXN(0, Math.Floor(x1 + dx));
            m_quad.SetYN(0, Math.Floor(y1 + dy));// - 150;
            m_quad.SetXN(1, Math.Floor(x2 + dx));
            m_quad.SetYN(1, Math.Floor(y1 + dy));// - 110;
            m_quad.SetXN(2, Math.Floor(x2 + dx));
            m_quad.SetYN(2, Math.Floor(y2 + dy));// - 300;
            m_quad.SetXN(3, Math.Floor(x1 + dx));
            m_quad.SetYN(3, Math.Floor(y2 + dy));// - 200;

            //m_SourceImage.apply_gamma_dir(m_gamma_lut);
        }

        bool didInit = false;
        public override void OnDraw(Graphics2D graphics2D)
        {
            if (!didInit)
            {
                didInit = true;
                OnInitialize();
            }

            if(m_gamma.value() != m_old_gamma)
            {
                m_gamma_lut.SetGamma(m_gamma.value());
                ImageBMPIO.LoadImageData("spheres.bmp", m_SourceImage);
                //m_SourceImage.apply_gamma_dir(m_gamma_lut);
                m_old_gamma = m_gamma.value();
            }

            ImageBuffer pixf = new ImageBuffer();
            switch (graphics2D.DestImage.BitDepth)
            {
                case 24:
                    pixf.Attach(graphics2D.DestImage, new BlenderBGR());
                    break;

                case 32:
                    pixf.Attach(graphics2D.DestImage, new BlenderBGRA());
                    break;

                default:
                    throw new NotImplementedException();
            }

            ImageClippingProxy clippingProxy = new ImageClippingProxy(pixf);

            clippingProxy.clear(new RGBA_Doubles(1, 1, 1));

            if(m_trans_type.cur_item() < 2)
            {
                // For the affine parallelogram transformations we
                // calculate the 4-th (implicit) point of the parallelogram
                m_quad.SetXN(3, m_quad.GetXN(0) + (m_quad.GetXN(2) - m_quad.GetXN(1)));
                m_quad.SetYN(3, m_quad.GetYN(0) + (m_quad.GetYN(2) - m_quad.GetYN(1)));
            }

            // draw a background to show how the alpha is working
            int RectWidth = 70;
            int xoffset = 50;
            int yoffset = 50;
            for (int i = 0; i < 7; i++)
            {
                for (int j = 0; j < 7; j++)
                {
                    if ((i + j) % 2 != 0)
                    {
                        VertexSource.RoundedRect rect = new VertexSource.RoundedRect(i * RectWidth + xoffset, j * RectWidth + yoffset,
                            (i + 1) * RectWidth + xoffset, (j + 1) * RectWidth + yoffset, 2);
                        rect.normalize_radius();

                        g_rasterizer.add_path(rect);
                        agg_renderer_scanline.Default.render_scanlines_aa_solid(clippingProxy, g_rasterizer, g_scanline, new RGBA_Bytes(.2, .2, .2));
                    }
                }
            }

            //--------------------------
            // Render the "quad" tool and controls
            g_rasterizer.add_path(m_quad);
            agg_renderer_scanline.Default.render_scanlines_aa_solid(clippingProxy, g_rasterizer, g_scanline, new RGBA_Bytes(0, 0.3, 0.5, 0.1));

            // Prepare the polygon to rasterize. Here we need to fill
            // the destination (transformed) polygon.
            g_rasterizer.SetVectorClipBox(0, 0, Width, Height);
            g_rasterizer.reset();
            int b = 0;
            g_rasterizer.move_to_d(m_quad.GetXN(0)-b, m_quad.GetYN(0)-b);
            g_rasterizer.line_to_d(m_quad.GetXN(1)+b, m_quad.GetYN(1)-b);
            g_rasterizer.line_to_d(m_quad.GetXN(2)+b, m_quad.GetYN(2)+b);
            g_rasterizer.line_to_d(m_quad.GetXN(3)-b, m_quad.GetYN(3)+b);

            //typedef agg::span_allocator<color_type> span_alloc_type;
            span_allocator sa = new span_allocator();
            image_filter_bilinear filter_kernel = new image_filter_bilinear();
            ImageFilterLookUpTable filter = new ImageFilterLookUpTable(filter_kernel, true);

            ImageBufferAccessorClamp source = new ImageBufferAccessorClamp(m_SourceImage);

            guiSurface.start_timer();

            switch(m_trans_type.cur_item())
            {
                case 0:
                {
            /*
                    agg::trans_affine tr(m_quad.polygon(), g_x1, g_y1, g_x2, g_y2);

                    typedef agg::span_interpolator_linear<agg::trans_affine> interpolator_type;
                    interpolator_type interpolator(tr);

                    typedef image_filter_2x2_type<source_type, 
                                                  interpolator_type> span_gen_type;
                    span_gen_type sg(source, interpolator, filter);
                    agg::render_scanlines_aa(g_rasterizer, g_scanline, rb_pre, sa, sg);
             */
                    break;
                }

                case 1:
                {
            /*
                    agg::trans_affine tr(m_quad.polygon(), g_x1, g_y1, g_x2, g_y2);

                    typedef agg::span_interpolator_linear<agg::trans_affine> interpolator_type;
                    typedef image_resample_affine_type<source_type> span_gen_type;

                    interpolator_type interpolator(tr);
                    span_gen_type sg(source, interpolator, filter);
                    sg.blur(m_blur.value());
                    agg::render_scanlines_aa(g_rasterizer, g_scanline, rb_pre, sa, sg);
             */
                    break;
                }

                case 2:
                {
            /*
                    agg::trans_perspective tr(m_quad.polygon(), g_x1, g_y1, g_x2, g_y2);
                    if(tr.is_valid())
                    {
                        typedef agg::span_interpolator_linear_subdiv<agg::trans_perspective> interpolator_type;
                        interpolator_type interpolator(tr);

                        typedef image_filter_2x2_type<source_type,
                                                      interpolator_type> span_gen_type;
                        span_gen_type sg(source, interpolator, filter);
                        agg::render_scanlines_aa(g_rasterizer, g_scanline, rb_pre, sa, sg);
                    }
             */
                    break;
                }

                case 3:
                {
            /*
                    agg::trans_perspective tr(m_quad.polygon(), g_x1, g_y1, g_x2, g_y2);
                    if(tr.is_valid())
                    {
                        typedef agg::span_interpolator_trans<agg::trans_perspective> interpolator_type;
                        interpolator_type interpolator(tr);

                        typedef image_filter_2x2_type<source_type, 
                                                      interpolator_type> span_gen_type;
                        span_gen_type sg(source, interpolator, filter);
                        agg::render_scanlines_aa(g_rasterizer, g_scanline, rb_pre, sa, sg);
                    }
             */
                    break;
                }

                case 4:
                {
                    //typedef agg::span_interpolator_persp_lerp<> interpolator_type;
                    //typedef agg::span_subdiv_adaptor<interpolator_type> subdiv_adaptor_type;

                    span_interpolator_persp_lerp interpolator = new span_interpolator_persp_lerp(m_quad.polygon(), g_x1, g_y1, g_x2, g_y2);
                    span_subdiv_adaptor subdiv_adaptor = new span_subdiv_adaptor(interpolator);

                    span_image_resample sg = null;
                    if(interpolator.is_valid())
                    {
                        switch(source.SourceImage.BitDepth)
                        {
                            case 24:
                                sg = new span_image_resample_rgb(source, subdiv_adaptor, filter);
                                break;

                            case 32:
                                sg = new span_image_resample_rgba(source, subdiv_adaptor, filter);
                                break;
                        }

                        sg.blur(m_blur.value());
                        agg_renderer_scanline.Default.GenerateAndRender(g_rasterizer, g_scanline, clippingProxy, sa, sg);
                    }
                    break;
                }

                case 5:
                {
            /*
                    typedef agg::span_interpolator_persp_exact<> interpolator_type;
                    typedef agg::span_subdiv_adaptor<interpolator_type> subdiv_adaptor_type;

                    interpolator_type interpolator(m_quad.polygon(), g_x1, g_y1, g_x2, g_y2);
                    subdiv_adaptor_type subdiv_adaptor(interpolator);

                    if(interpolator.is_valid())
                    {
                        typedef image_resample_type<source_type, 
                                                    subdiv_adaptor_type> span_gen_type;
                        span_gen_type sg(source, subdiv_adaptor, filter);
                        sg.blur(m_blur.value());
                        agg::render_scanlines_aa(g_rasterizer, g_scanline, rb_pre, sa, sg);
                    }
             */
                    break;
                }
            }

            double tm = guiSurface.elapsed_time();
            //pixf.apply_gamma_inv(m_gamma_lut);

            gsv_text t = new gsv_text();
            t.SetFontSize(10.0);

            conv_stroke pt = new conv_stroke(t);
            pt.width(1.5);

            string buf = string.Format("{0:F2} ms", tm);
            t.start_point(10.0, 70.0);
            t.text(buf);

            g_rasterizer.add_path(pt);
            agg_renderer_scanline.Default.render_scanlines_aa_solid(clippingProxy, g_rasterizer, g_scanline, new RGBA_Bytes(0, 0, 0));

            //--------------------------
            //m_trans_type.Render(g_rasterizer, g_scanline, clippingProxy);
            //m_gamma.Render(g_rasterizer, g_scanline, clippingProxy);
            //m_blur.Render(g_rasterizer, g_scanline, clippingProxy);
            base.OnDraw(graphics2D);
        }

        public override void OnMouseDown(MouseEventArgs mouseEvent)
        {
            base.OnMouseDown(mouseEvent);

            if (mouseEvent.Button == MouseButtons.Left)
            {
            	m_quad.OnMouseDown(mouseEvent);
                if (MouseCaptured)
                {
                    Invalidate();
                }
            }
        }

        public override void OnMouseMove(MouseEventArgs mouseEvent)
        {
            if (mouseEvent.Button == MouseButtons.Left)
            {
            	m_quad.OnMouseMove(mouseEvent);
                if (MouseCaptured)
                {
                    Invalidate();
                }
            }

            base.OnMouseMove(mouseEvent);
        }

        public override void OnMouseUp(MouseEventArgs mouseEvent)
        {
        	m_quad.OnMouseUp(mouseEvent);
            if (MouseCaptured)
            {
                Invalidate();
            }

            base.OnMouseUp(mouseEvent);
        }

        public override void OnKeyDown(AGG.UI.KeyEventArgs keyEvent)
        {
            if(keyEvent.KeyCode == Keys.Space)
            {
                double cx = (m_quad.GetXN(0) + m_quad.GetXN(1) + m_quad.GetXN(2) + m_quad.GetXN(3)) / 4;
                double cy = (m_quad.GetYN(0) + m_quad.GetYN(1) + m_quad.GetYN(2) + m_quad.GetYN(3)) / 4;
                Affine tr = Affine.NewTranslation(-cx, -cy);
                tr *= Affine.NewRotation(Math.PI / 2.0);
                tr *= Affine.NewTranslation(cx, cy);
                double xn0 = m_quad.GetXN(0); double yn0 = m_quad.GetYN(0);
                double xn1 = m_quad.GetXN(1); double yn1 = m_quad.GetYN(1);
                double xn2 = m_quad.GetXN(2); double yn2 = m_quad.GetYN(2);
                double xn3 = m_quad.GetXN(3); double yn3 = m_quad.GetYN(3);
                tr.transform(ref xn0, ref yn0);
                tr.transform(ref xn1, ref yn1);
                tr.transform(ref xn2, ref yn2);
                tr.transform(ref xn3, ref yn3);
                m_quad.SetXN(0, xn0); m_quad.SetYN(0, yn0);
                m_quad.SetXN(1, xn1); m_quad.SetYN(1, yn1);
                m_quad.SetXN(2, xn2); m_quad.SetYN(2, yn2);
                m_quad.SetXN(3, xn3); m_quad.SetYN(3, yn3);
                Invalidate();
            }

            base.OnKeyDown(keyEvent);
        }

		public static void StartDemo()
		{
            GuiHalFactory.SetGuiBackend(GuiHalFactory.KnownGuiFactoriesIndexes.WindowsFormsBitmap);

            GuiHalSurface primaryWindow = GuiHalFactory.CreatePrimarySurface(600, 600, GuiHalSurface.CreateFlags.Resizable, GuiHalSurface.PixelFormat.PixelFormatBgr24);
            //GuiHalSurface primaryWindow = GuiHalFactory.CreatePrimarySurface(600, 600, GuiHalSurface.CreateFlags.Resizable, GuiHalSurface.PixelFormat.PixelFormatBgra32);

            primaryWindow.Caption = "AGG Example. Image Transformations with Resampling";

            string img_name = "spheres.bmp";
            if (!ImageBMPIO.LoadImageData(img_name, image_resample_application.m_SourceImage))
            {
                string buf;
                buf = "File not found: "
                    + img_name
                    + ".bmp"
                    + ". Download http://www.antigrain.com/" + img_name + ".bmp" + "\n"
                    + "or copy it from another directory if available.";
                primaryWindow.ShowSystemMessage(buf);
            }
            else
            {
                // give the image some alpha. [4/6/2009 lbrubaker]
                ImageBuffer image32 = new ImageBuffer(image_resample_application.m_SourceImage.Width, image_resample_application.m_SourceImage.Height, 32, new BlenderBGRA());
                int offset;
                byte[] source = image_resample_application.m_SourceImage.GetBuffer(out offset);
                byte[] dest = image32.GetBuffer(out offset);
                for (int y = 0; y < image32.Height; y++)
                    for (int x = 0; x < image32.Width; x++)
                {
                    int i = y * image32.Width + x;
                    dest[i * 4 + 0] = source[i * 3 + 0];
                    dest[i * 4 + 1] = source[i * 3 + 1];
                    dest[i * 4 + 2] = source[i * 3 + 2];
                    Vector2D pixel = new Vector2D(x, y);
                    Vector2D center = new Vector2D(image32.Width / 2, image32.Height / 2);
                    Vector2D delta = pixel - center;
                    int length = (int)Math.Min(delta.GetLength() * 3, 255);
                    dest[i * 4 + 3] = (byte)length;
                }
                // and set our new image with alpha
                image_resample_application.m_SourceImage = image32;
                //image_resample_application.m_SourceImage.SetBlender(new BlenderBGR());
            }
        
            primaryWindow.AddChild(new image_resample_application(primaryWindow));
            primaryWindow.Run();
        }

		[STAThread]
        public static void Main(string[] args)
        {
        	StartDemo();
        }
    };
}
